#include "cudaWorker.h"
#include "../config.h"
#include "../RLE/RLE.h"
#include "../BWT/BWT.h"
#include "../ModeToFront/MTF.h"
#include "../Huffman/Huffman.h"
#include "../Adler-32 checksum/Adler32.h"

#include <iostream>
#include <fstream>
using namespace std;

#include "RLE_MN5.cu"
//#include "BWT.cu"
//#include "MTF.cu"
//#include "RLE_AB.cu"
//#include "Huffman.cu"

//TODO:makro nebo funkci na vypocet max. velikosti zkomprimovanyho bloku

__global__ void kernelCompression(unsigned int size, const unsigned char *arrIn, unsigned char *arrOut, unsigned char *exchangeBuffer)
{
    RLE_Encode_MN5(arrIn, size, arrOut+sizeof(unsigned int), ((unsigned int *)arrOut)[0]);
    //unsigned int enc_len;
    //RLE_Encode_MN5(arrIn, size, exchangeBuffer, enc_len);
    //BWT_Encode(enc_len, exchangeBuffer, arrOut+sizeof(unsigned int), ((unsigned int *)arrOut)[0]);
}

void CudaWorker::init(int bytes)
{
    cudaMalloc((void**)&input, bytes);
    cudaMalloc((void**)&output, int(double(bytes)*2.5)+540);   // zabalenej soubor se muze i zvetsit (max. 2.5N+538/blok)
    cudaMalloc((void**)&exchng, int(double(bytes)*2.5)+540);   // zabalenej soubor se muze i zvetsit (max. 2.5N+538/blok)
}

void CudaWorker::doCudaCompression(ifstream &fin, ofstream &fout)
{
    //
    // 1. zjistit velikost souboru
    fin.seekg(0, ios_base::end);
    size_t fileSize = fin.tellg();
    fin.seekg(0, ios_base::beg);
    //
    // 2. zjistit velikost pameti na gpu
    int device;
    cudaDeviceProp prop;
    cudaGetDevice(&device);
    cudaGetDeviceProperties(&prop, device);
    //prop.totalGlobalMem
    //
    // 3. naalokovat, kolik se vleze do gpu a nakopirovat tam data ze souboru (pozor-potreba uklada i vstup i vystup+tempy)
    init(fileSize);
    unsigned char *buffer = new unsigned char[fileSize];
    fin.read((char *)buffer, fileSize);
    cudaMemcpy(input, buffer, fileSize, cudaMemcpyHostToDevice);
    //
    // 4. pustit kernel, kde bude vsechno krome checksumu, coz se spocita na CPU
    kernelCompression<<<1,1>>>(Config::BLOCK_SIZE, input, output, exchng);
    cudaThreadSynchronize();
    unsigned char *arr = new unsigned char[int(double(fileSize)*2.5)+540];
    cudaMemcpy(arr, output, int(double(fileSize)*2.5)+540, cudaMemcpyDeviceToHost);
    //
    // 5. ulozit
    unsigned int enc_len = ((unsigned int *)arr)[0];
    //fout.write("PRC", 3);
    //fout.write((char *)&enc_len, sizeof(unsigned int));	// encoded block length (for faster decompression - especially in parallel)
    fout.write((char *)arr+sizeof(unsigned int), enc_len);				// encoded block
//    fout.write((char *)&checksum, sizeof(unsigned int));// checksum of the encoded block
    //
    clean();
    delete [] buffer;
    delete [] arr;
}

void CudaWorker::doCudaDecompression(ifstream &fin, ofstream &fout)
{
    //
}

void CudaWorker::clean()
{
    cudaFree(input);
    cudaFree(output);
    cudaFree(exchng);
    cudaDeviceReset();
}